package ace.cmp.converter.spring.core;

import ace.cmp.converter.api.AceConverter;
import ace.cmp.converter.api.ConverterService;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
import lombok.SneakyThrows;

/**
 * @author caspar
 * @date 2023/4/4 15:04
 */
public class ConverterServiceImpl implements ConverterService {
  private static Integer DEFAULT_MAP_SIZE = 10;
  private final ConcurrentMap<Class, ConcurrentMap<Class, AceConverter>> beanCopierMap =
      new ConcurrentHashMap<>(DEFAULT_MAP_SIZE);
  private final ConverterFactory converterFactory;
  private static final String CONVERT_METHOD_NAME = "convert";

  public ConverterServiceImpl(List<AceConverter> converters, ConverterFactory converterFactory) {
    this.converterFactory = converterFactory;
    if (converters != null && converters.size() > 0) {
      for (AceConverter converter : converters) {
        List<Method> methods = Arrays.asList(converter.getClass().getMethods());
        Method method =
            methods.stream()
                .filter(p -> p.getName().equals(CONVERT_METHOD_NAME))
                .filter(p -> p.getReturnType().equals(Object.class) == false)
                .filter(p -> p.getParameters().length == 1)
                .findFirst()
                .get();

        Class sClass = method.getParameters()[0].getType();
        Class tClass = method.getReturnType();
        ConcurrentMap<Class, AceConverter> map =
            beanCopierMap.computeIfAbsent(
                sClass, (key) -> new ConcurrentHashMap<>(DEFAULT_MAP_SIZE));
        map.put(tClass, converter);
      }
    }
  }

  @Override
  public <S, T> AceConverter<S, T> getOrCreate(Class<S> sClass, Class<T> tClass) {
    ConcurrentMap<Class, AceConverter> map =
        beanCopierMap.computeIfAbsent(sClass, (key) -> new ConcurrentHashMap<>(DEFAULT_MAP_SIZE));
    AceConverter<S, T> converter =
        map.computeIfAbsent(tClass, (key) -> converterFactory.create(sClass, tClass));
    return converter;
  }

  @SneakyThrows
  @Override
  public <S, T> AceConverter<List<S>, List<T>> getDefaultListConverter(
      Class<S> sClass, Class<T> tClass) {
    return sourceList ->
        sourceList.stream()
            .map(source -> this.getOrCreate(sClass, tClass).convert(source))
            .collect(Collectors.toList());
  }

  @SneakyThrows
  @Override
  public <S, T, Id> AceConverter<Map<Id, S>, Map<Id, T>> getDefaultMapConverter(
      Class<S> sClass, Class<T> tClass) {
    return map ->
        map.entrySet().stream()
            .collect(
                HashMap::new,
                (m, entry) -> {
                  Id id = entry.getKey();
                  S source = entry.getValue();
                  T dest = this.getOrCreate(sClass, tClass).convert(source);
                  m.put(id, dest);
                },
                HashMap::putAll);
  }
}
